﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using AirDrive.Companion.Properties;

namespace AirDrive.Companion
{
    class AirDriveCompanion
    {
        [DllImport("user32.dll")]
        private static extern int ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        private const int SW_RESTORE = 9;

        public event Action<Drive> DriveConnected;
        public event Action<Drive> DriveUpdated;
        public event Action DrivesCleaned;

        private AutoDiscoveryServer _discoveryServer;
        private List<Drive> _drives;
        private Queue<Drive> _connectedDriveQueue;
        private Drive _justConnectedDrive;
        private Drive _oldestDrive;
        private object _sync = new object();

        private Timer _timer;

        public bool AutoOpenConnectedDrive 
        {
            get { return Settings.Default.AutoOpenConnectedDrive; }
            set
            {
                Settings.Default.AutoOpenConnectedDrive = value;
                Settings.Default.Save();
            }
        }

        public List<Drive> Drives
        {
            get { return _drives; }
        }

        public AirDriveCompanion()
        {
            _drives = new List<Drive>();
            _connectedDriveQueue = new Queue<Drive>();
            _timer = new Timer(CheckDrives);
        }

        private void CheckDrives(object state)
        {
            ClearOldDrives();
            
            lock (_sync)
            {
                if (_drives.Count == 0)
                    _oldestDrive = null;
                else
                {
                    _oldestDrive = _drives.OrderBy(x => x.LastHeartbeat).First();
                    _timer.Change(GetDueTime(_oldestDrive.LastHeartbeat), Timeout.Infinite);
                }
            }
        }

        private int GetDueTime(DateTime lastHeartbeat)
        {
            int dueTime = 0;
            DateTime nextCheck = lastHeartbeat.AddSeconds(7);
            DateTime now = DateTime.Now;
            if (nextCheck > now)
                dueTime = (int)nextCheck.Subtract(now).TotalMilliseconds;

            return dueTime;
        }
        
        public void Start()
        {
            _discoveryServer = new AutoDiscoveryServer();
            _discoveryServer.Heartbeat += _discoveryServer_Heartbeat;
            _discoveryServer.Start();
        }
        
        private void _discoveryServer_Heartbeat(Drive drive)
        {
            lock (_sync)
            {
                int i = _drives.FindIndex(x => x.Endpoint.Equals(drive.Endpoint));
                if (i == -1)
                {
                    _drives.Add(drive);
                    _connectedDriveQueue.Enqueue(drive);

                    if (_oldestDrive == null)
                    {
                        _oldestDrive = drive;
                        _timer.Change(GetDueTime(_oldestDrive.LastHeartbeat), Timeout.Infinite);
                    }

                    if (DriveConnected != null)
                        DriveConnected(drive);

                    if (AutoOpenConnectedDrive)
                        OpenDrive(drive);
                }
                else
                {
                    _drives[i] = drive;
                    if (DriveUpdated != null)
                        DriveUpdated(drive);
                }
            }
        }

        public void OpenJustConnectedDrive()
        {
            if (_connectedDriveQueue.Count > 0)
            {
                _justConnectedDrive = _connectedDriveQueue.Dequeue();
                OpenDrive(_justConnectedDrive);
            }
        }

        public void JustConnectedDriveIgnored()
        {
            _connectedDriveQueue.Dequeue();
        }

        public void OpenDrive(Drive drive)
        {
            if (drive != null)
            {
                var shellType = Type.GetTypeFromProgID("Shell.Application");
                object shell = Activator.CreateInstance(shellType);
                try
                {
                    var windows = shellType.InvokeMember("Windows", BindingFlags.InvokeMethod, null, shell, null);
                    var windowsType = windows.GetType();
                    int count = (int)windows.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, windows, null);
                    for (int i = 0; i < count; i++)
                    {
                        var wndw = windowsType.InvokeMember("Item", BindingFlags.InvokeMethod, null, windows, new object[] { i });
                        if (wndw == null) continue;

                        var wndwType = wndw.GetType();
                        var processName = System.IO.Path.GetFileName((string)wndwType.InvokeMember("FullName", BindingFlags.GetProperty, null, wndw, null));
                        if (processName.ToLower() == "explorer.exe")
                        {
                            var doc = wndwType.InvokeMember("document", BindingFlags.GetProperty, null, wndw, null);
                            var docType = doc.GetType();
                            var focItem = docType.InvokeMember("focuseditem", BindingFlags.GetProperty, null, doc, null);
                            var focItemType = focItem.GetType();
                            var path = (string)focItemType.InvokeMember("path", BindingFlags.GetProperty, null, focItem, null);

                            if (!string.IsNullOrEmpty(path) && path.StartsWith(string.Format("ftp://{0}", drive.Endpoint.Address)))
                            {
                                var wscriptType = Type.GetTypeFromProgID("WScript.Shell");
                                var wScript = Activator.CreateInstance(wscriptType);
                                IntPtr hwnd = new IntPtr((long)wndwType.InvokeMember("HWND", BindingFlags.GetProperty, null, wndw, null));

                                wndwType.InvokeMember("Refresh", BindingFlags.InvokeMethod, null, wndw, null);
                                ShowWindow(hwnd, SW_RESTORE);
                                SetForegroundWindow(hwnd);
                                return;
                            }
                        }
                    }
                }
                finally
                {
                    Marshal.FinalReleaseComObject(shell);
                }

                //if not already opened, open new window
                Process.Start(string.Format("\"{0}\"", Settings.Default.FileBrowserPath), string.Format("ftp://{0}", drive.Endpoint));
            }
        }

        public void ClearOldDrives()
        {
            lock (_sync)
            {
                int count = _drives.Count;
                foreach (var drive in _drives.Where(x => DateTime.Now.Subtract(x.LastHeartbeat).TotalSeconds > 7).ToArray())
                {
                    Debug.WriteLine("Last heartbeat: {0}", DateTime.Now.Subtract(drive.LastHeartbeat).TotalSeconds.ToString());
                    _drives.Remove(drive);
                }

                if (count != _drives.Count && DrivesCleaned != null)
                    DrivesCleaned();
            }
        }

        public void Close()
        {
            _discoveryServer.Heartbeat -= _discoveryServer_Heartbeat;
            _discoveryServer.Stop();

            _drives.Clear();
            _connectedDriveQueue.Clear();
            _justConnectedDrive = null;
        }
    }
}
